Stripe Production Setup Guide for Atom SaaS
This guide explains how to set up the Stripe products and prices required to support the Atom SaaS billing engine, including metered "Managed AI" usage.
1. Product & Price Configuration
You need to create the following products and prices in your Stripe Dashboard.
Core Subscription Plans
Create one **Recurring** product named "Atom SaaS" with the following tiers:
| Plan | Price (Monthly) | Stripe Price Model | Atoms Equivalent |
|---|---|---|---|
| **Solo** | $19.00 | Recurring | STRIPE_PRICE_ID_SOLO |
| **Team** | $79.00 | Recurring | STRIPE_PRICE_ID_TEAM |
| **Enterprise** | $299.00 | Recurring | STRIPE_PRICE_ID_ENTERPRISE |
Managed AI Usage (Metered)
We use two distinct Metered Prices to support the "Bundle" vs "Pay-As-You-Go" strategies.
1. For Model A (Bundled + Overage)
This price is attached to the **Solo** and **Team** subscriptions ($50/mo add-on base).
- **Product Name**: "Managed AI Tokens (Bundled)"
- **Pricing Model**: **Tiered** (Usage-based)
- **Tiers**:
- **First 5,000,000 units**: $0.00 (Free Bundle)
- **Any usage after**: $0.000005 per unit ($5 per 1M)
- **Env Variable**:
STRIPE_PRICE_ID_MANAGED_AI_BUNDLED
2. For Model B (Pay-As-You-Go)
This price is attached to **Enterprise** or custom separate subscriptions.
- **Product Name**: "Managed AI Tokens (Pure)"
- **Pricing Model**: **Standard** (Usage-based)
- **Price**: $0.00002 per unit (approx $20/1M - includes margin)
- **Env Variable**:
STRIPE_PRICE_ID_MANAGED_AI_PURE
---
2. Webhook Setup
Step 1: Add Endpoint
Go to Developers -> Webhooks and click "Add Endpoint".
- **URL**:
https://your-domain.com/api/billing/webhook - **Events to listen for**:
checkout.session.completedcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_succeeded
Step 2: Configure Secret
Copy the **Signing Secret** (whsec_...) and add it to your Vault secrets:
atom-cli secrets set STRIPE_WEBHOOK_SECRET=whsec_...---
3. Developer Integration
Linking Tenant to Stripe Usage
Atom uses the stripe_usage_item_id to report consumption. When a subscription is created, you must store the id of the metered price item on the Tenant model to enable real-time reporting.
# During checkout.session.completed
subscription = stripe.Subscription.retrieve(session.subscription)
# Determine strictly metered usage item ID based on plan/logic
# In a real app, this logic maps Plan -> Price ID
price_id_bundled = os.getenv('STRIPE_PRICE_ID_MANAGED_AI_BUNDLED')
price_id_pure = os.getenv('STRIPE_PRICE_ID_MANAGED_AI_PURE')
# Find the usage item in the subscription
usage_item = next((item for item in subscription['items']['data']
if item.price.id in [price_id_bundled, price_id_pure]), None)
if usage_item:
# Store usage_item.id (e.g. "si_...") on the Tenant model
tenant.stripe_usage_item_id = usage_item.id
tenant.stripe_subscription_id = subscription.id---
4. Reporting Usage (Cron Job)
To actually bill customers, you must push aggregated usage to Stripe periodically (e.g., hourly).
`backend-saas/scripts/report_usage.py`
Create a script that:
- Aggregates unbilled
TokenUsagerecords for each tenant. - Pushes the sum to Stripe's Usage Record API.
- Marks tokens as
billed=True.
import stripe
import os
from sqlalchemy import func
from core.models import TokenUsage, Tenant
from core.database import SessionLocal
def report_usage():
db = SessionLocal()
stripe.api_key = os.getenv("STRIPE_SECRET_KEY")
# 1. Aggregate unbilled usage by tenant
usage_aggregates = (
db.query(
TokenUsage.tenant_id,
func.sum(TokenUsage.input_tokens + TokenUsage.output_tokens).label("total_tokens")
)
.filter(TokenUsage.billed == False)
.group_by(TokenUsage.tenant_id)
.all()
)
for tenant_id, total_tokens in usage_aggregates:
tenant = db.query(Tenant).filter(Tenant.id == tenant_id).first()
if not tenant or not tenant.stripe_usage_item_id:
continue
try:
# 2. Push to Stripe
stripe.SubscriptionItem.create_usage_record(
tenant.stripe_usage_item_id,
quantity=int(total_tokens),
timestamp=int(time.time()),
action='increment',
)
# 3. Mark as billed
db.query(TokenUsage).filter(
TokenUsage.tenant_id == tenant_id,
TokenUsage.billed == False
).update({"billed": True})
db.commit()
print(f"Reported {total_tokens} for tenant {tenant_id}")
except Exception as e:
print(f"Failed to report usage for {tenant_id}: {e}")
db.rollback()
if __name__ == "__main__":
report_usage()